package com.heavenick.core.dao;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.apache.ibatis.session.SqlSession;

import com.heavenick.core.exception.DaoException;

public class DaoProxy implements InvocationHandler, Serializable {

	private SqlSession sqlSession;

	private <T> DaoProxy(SqlSession sqlSession) {
		this.sqlSession = sqlSession;
	}

	private static final long serialVersionUID = 4803400744997279155L;

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if (method.getDeclaringClass() == Object.class) {
			return method.invoke(this, args);
		}
		final Class<?> declaringInterface = findDeclaringInterface(proxy,
				method);
		DaoProxyMethod daomethod = new DaoProxyMethod(declaringInterface,
				method, sqlSession);
		return daomethod.excutor(args);
	}

	private Class<?> findDeclaringInterface(Object proxy, Method method) {
		Class<?> declaringInterface = null;
		for (Class<?> iface : proxy.getClass().getInterfaces()) {
			try {
				Method m = iface.getMethod(method.getName(),
						method.getParameterTypes());
				if (declaringInterface != null) {
					throw new DaoException(
							"Two dao interfaces contain the identical method signature for "
									+ method);
				} else if (m != null) {
					declaringInterface = iface;
				}
			} catch (Exception e) {
				// Intentionally ignore.
				// This is using exceptions for flow control,
				// but it's definitely faster.
			}
		}
		if (declaringInterface == null) {
			throw new DaoException(
					"Could not find interface with the given method " + method);
		}
		return declaringInterface;
	}

	@SuppressWarnings("unchecked")
	public static <T> T newDaoProxy(Class<T> daoInterface, SqlSession sqlSession) {
		ClassLoader classLoader = daoInterface.getClassLoader();
		Class<?>[] interfaces = new Class[] { daoInterface };
		DaoProxy proxy = new DaoProxy(sqlSession);
		return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
	}
}